/******************************************************************************
 * \brief	私有通信协议客户端函数接口
 * \note	File format: UTF-8，中文编码：UTF-8
 * \author	将狼才鲸
 * \date	2023-03-24
 ******************************************************************************/

/*********************************** 头文件 ***********************************/
#include "scp_client.h"

/********************************** 私有函数 **********************************/
/**
 * \brief	一个已有的数据包中如果没有连接ID，则赋值一个新的
 * \details	这个包的连接ID和通信连接ID有关
 */
static uint32_t scp_newcid(scp_t *pscp)
{
	/* 通信链路的连接ID加1，包的连接ID使用通信连接ID + 随机值 */
	uint32_t id = (((++pscp->CID) & 0xffff) << 16) | (rand() & 0xffff);

	return htonl(id);
}

/**
 * \brief	将要发送的单个消息（或多发多收的消息系列）添加到写队列
 * \param	pscp:	通信连接
 * \param	seg:	单次要发送的通信消息，或一系列消息，该消息已事先分配过空间
 * \return	已加入队列的消息数量；单条消息的话正常处理后会为1
 */
static int scp_tx_addto_writelist(scp_t *pscp, segment_t *seg)
{
	int sec, count = 0;
	seg_pkt_head_t *pkt;
	irq_flag lock;

	irq_disable(lock);

	while (seg) {
		/* 如果是一系列消息，则循环将这个消息系列都加入到写队列中，否则只执行一次 */
		if (seg->TT == TT_ACKONLY || seg->TT == TT_RESPONDS) {
			/* 如果包类型正确 */
			pkt = (seg_pkt_head_t *)scpseg_data(seg);	/* 获取实际包数据 */

			if (!pkt->CID) {
				/* 赋值新的连接ID，每个发送包的ID都不一样，发送包和响应包ID相同 */
				pkt->CID = scp_newcid(pscp);
			}
			/* 段ID归零，实际发送时再赋值；发送包从1开始递增，相应包和发送包相同 */
			pkt->SID = 0;
			seg->LEN = SEG_PKT_HEAD_LEN + ntohl(pkt->LEN); /* 段中数据长度 */

			/* 从权限判断用户组 */
			if (pkt->SEC & SEC_UG)
				sec = SECLVL_URGENCY;
			else if (pkt->SEC & (SEC_SW | SEC_SR))
				sec = SECLVL_ADMIN;
			else if (pkt->SEC & (SEC_TW | SEC_TR))
				sec = SECLVL_OPERATOR;
			else {
				pkt->SEC = (SEC_PW | SEC_PR);
				sec = SECLVL_USER;
			}

			/* 将要发送的消息加入到不同的用户组的队列 */
			init_list_head(&seg->node);
			list_add_tail(&seg->node, &(pscp->wpkt_list[sec]));
			count++;
		} else {
			/* 如果传输包类型错误，则要重新组织发送包；当前这种情况下未做额外处理 */
			seg->responds = ACK_RACK;
		}

		seg = seg->next;	/* 处理该系列中下一条消息（很少需要使用该功能） */
	}

	irq_enable(lock);

	return count;
}

/**
 * \brief	从写数据包队列中取出一个要写的数据包，随后会发送出去
 */
static segment_t *tx_readfrom_list(scp_t *pscp)
{
	int i, found;
	segment_t *seg = NULL, *n;
	irq_flag lock;

	if (!scp_valid(pscp))	/* scp句柄有效 */
		return NULL;

	irq_disable(lock);

	found = 0;
	for (i = 0; !found && (i < SECLVL_COUNT); i++) {
		/* 循环整个用户组（通常只会使用操作员组） */
		list_head_t *head = &(pscp->wpkt_list[i]);	/* 写数据包队列 */
		if (!list_empty_careful(head)) {
			/* 判断队列非空 */
			list_for_each_entry_safe(seg, n, head, node) {
				/* 从队列中循环读出数据 */
				if ((seg->responds == ACK_NULL) && (!(seg->PHS & SEGMENT_TRANSFERING))
					&& (seg->PHS & (SEGMENT_TRANSMIT1 | SEGMENT_TRANSMIT2 | SEGMENT_TRANSMIT3))
					!= (SEGMENT_TRANSMIT1 | SEGMENT_TRANSMIT2 | SEGMENT_TRANSMIT3)) {
					/* 如果该数据包还没有响应，而且该包未发送，并且该包不处于已发送状态 */
					found = 1;
					break;
				}
			}
		}
	}

	irq_enable(lock);

	return found ? seg : NULL;
}

/**
 * \brief	判断是不是发送包序列中其它请求包对应的响应包
 * \param	pscp:	当前发送的协议句柄
 * \param	pkt:	当前已接收的响应包
 * \return	是当前发送包的响应包则返回假，是发送包序列中其它包的响应包则返回真
 */
static bool scp_pkt_old(scp_t *pscp, seg_pkt_head_t *pkt)
{
	int i;
	bool cidfound, sidfound;
	uint16_t PID = ntohs(pkt->PID); /* 协议ID */
	uint32_t CID = ntohl(pkt->CID); /* 连接ID */
	uint32_t SID = ntohl(pkt->SID); /* 段ID */

	if (SCP_QPID(PID) == SCP_PID_ACK) {
		/* 如果不是当前请求包的响应包 */
		return false;
	}

	/* 是其它发送包的响应包 */
	cidfound = false;
	for (i = 0; !cidfound && i < SCP_MAX_VCID_VALID; i++) {
		if (CID == pscp->VCID[i]) {
			/* 每个包的连接ID都不同，一个发送包和其对应的响应包ID相同 */
			cidfound = true;	/* 如果接收包的连接id在已发送包id数组中 */
			break;
		}
	}

	sidfound = false;
	for (i = 0; !sidfound && i < SCP_MAX_VSID_VALID; i++) {
		if (SID == pscp->VSID[i]) {
			/* 每个包的段ID都不同，一个发送包和其对应的响应包ID相同 */
			sidfound = true;	/* 如果接收包的段id在已发送包id数组中 */
			break;
		}
	}

	if (cidfound && sidfound)
		return true;

	if (!cidfound) {
		/* 如果所有已发送包都未匹配到该响应包，则将该响应包ID放到队列中最前面；
		  这种情况下算收到的异常数据吧 */
		for (i = SCP_MAX_VCID_VALID - 1; i > 0; i--)
			pscp->VCID[i] = pscp->VCID[i - 1];
		pscp->VCID[0] = CID;
	}

	if (!sidfound) {
		for (i = SCP_MAX_VSID_VALID - 1; i > 0; i--)
			pscp->VSID[i] = pscp->VSID[i - 1];
		pscp->VSID[0] = SID;
	}

	return false;
}

/**
 * \brief	打印包头信息，调试用
 */
static void scp_dump_packet_header(const char *msg, seg_pkt_head_t *pkt)
{
	uint32_t LEN = ntohl(pkt->LEN);

	pr_info("\n\t%s:\n"
			"\t\t CID  = 0x%08x \n"
			"\t\t LEN  = %d \n"
			"\t\t SID  = 0x%08x \n"
			// "\t\t TYPE = 0x%02x \n"
			// "\t\t SEC  = 0x%02x \n"
			"\t\t PID  = 0x%04x \n"
			"\t\t SUB  = 0x%08x \n",
			msg,
			ntohl(pkt->CID),
			LEN,
			ntohl(pkt->SID),
			// (unsigned)pkt->TYPE,
			// (unsigned)pkt->SEC,
			ntohs(pkt->PID),
			ntohl(pkt->SUB));
}

/**
 * \brief	打印包头信息，调试用
 */
static void scp_dump_packet(const char *msg, seg_pkt_head_t *pkt)
{
	scp_dump_packet_header(msg, pkt);	/* 打印包头信息 */
	if (ntohs(pkt->PID) == SCP_PID_ACK)	/* 如果是响应包 */
		scp_dump_packet_header("Is also ACK for", (seg_pkt_head_t *)(((uint8_t *)(pkt)) + SEG_PKT_HEAD_LEN));
}

/**
 * \brief	判断两个包是否相同
 * \param	ack:	接收包的响应包头（第二个包头）
 * \param	pkt:	发送包的包头
 */
static inline bool scp_is_same_pkt(seg_pkt_head_t *ack, seg_pkt_head_t *pkt)
{
	return true
			&& (ack->TYPE == pkt->TYPE)
			&& (ack->CID == pkt->CID)
			&& (ack->SID == pkt->SID)
			&& (ack->PID == pkt->PID)
			&& (ack->LEN == pkt->LEN);
}

/**
 * \brief	获取响应包，没有完整包则持续重入轮询
 * \param	pscp:	通信句柄
 * \param	segment:当前刚发送的包
 */
static int scp_run_receiving(scp_t *pscp, segment_t *segment)
{
	segment_t *seg_tx, *seg_rx;
	scp_dev_t *pdev = pscp->pdev;
	seg_pkt_head_t *pkt_tx, *pkt_rx;

	uint16_t PID;
	int32_t LEN;

	/** 1. 尝试接收数据包（从接收队列中读取） */
	seg_rx = scpdevs_read(pdev, RWMODE_RESPOND);
	if (seg_rx == NULL)
		return -EPERM;	/* 未收到数据包直接返回，再次进入此函数轮询 */

	/* 已获取到响应包 */
	pkt_rx = (seg_pkt_head_t *)scpseg_data(seg_rx);	/* 从接收数据段中提取数据包 */

	PID = ntohs(pkt_rx->PID);	/* 除了回环包，一般都是SCP_PID_ACK */
	LEN = ntohl(pkt_rx->LEN) + SEG_PKT_HEAD_LEN;

	/** 2. 检查包响应是否正确 */
	seg_tx = segment;	/* 当前刚发送的包，后面会检查收到的响应包是不是之前也收到过 */

	if (scp_pkt_old(pscp, pkt_rx)) {
		/* 是发送包序列中其它请求包对应的响应包，很少进入这里 */
		seg_tx = NULL;	/* 本次发送包重新从已发送队列中获取 */
	}

	// #if defined(CONFIG_USR_SOC_SCP_CRC32)
	// if ( !Scp_PktCrc(pkt_rx, false) ) {
	// seg_tx = NULL;
	// }
	// #endif

	if (!seg_tx) {
		/* 是发送包序列中其它请求包对应的响应包，很少进入这里 */
		seg_tx = segment;	/* 当前刚发送的包 */
		while (seg_tx) {
			if (seg_tx == seg_rx)	/* ??? */
				break;
			seg_tx = seg_tx->next;	/* 如果是发送包系列则循环处理（很少使用此功能） */
		}
		if (!seg_tx) {
			scpseg_free(seg_rx);
		}
		return -EFAULT;
	}

	/* 获取到响应包，打印包头信息 */
	scp_dump_packet("Client received ACK", pkt_rx);

	/* 判断是否是对应的响应包 */
	seg_tx = segment;
	while (seg_tx && seg_rx) {
		if (seg_tx->respseg || seg_tx->responds != ACK_NULL) {
			/* 如果当前发送包已有响应包，则循环判断这一系列发送的包（该功能很少使用） */
			seg_tx = seg_tx->next;
			continue;
		}

		if (seg_rx == seg_tx) {
				/* ??? */
			if (PID == SCP_PID_ACK) {
				/* 如果接受包是响应包 */
				seg_tx->responds = (int)(uint8_t)(ntohl(pkt_rx->SUB));
			} else {
				seg_tx->responds = ACK_ACK1;
			}
		} else {
			/* 判断是否是对应的响应包 */
			pkt_tx = (seg_pkt_head_t *)scpseg_data(seg_tx);
			if (PID == SCP_PID_ACK) {
				/* 如果接收包是响应包 */
				/* 响应包中携带的原发送包包头紧跟在响应包包头后面 */
				uint8_t *ack_data = (uint8_t *)(pkt_rx) + SEG_PKT_HEAD_LEN;	/* 原发送包 */
				if (scp_is_same_pkt((seg_pkt_head_t *)ack_data, pkt_tx)) {
					/* 如果响应包的包头（第二个包头）和发送包的包头一样 */
					/* 该发送包已正确响应 */
					seg_tx->responds = (int)(uint8_t)(ntohl(pkt_rx->SUB));
				}
			} else if ((pkt_rx->TYPE == pkt_tx->TYPE) && (pkt_rx->CID == pkt_tx->CID)
					 && (pkt_rx->SUB == pkt_tx->SID)
					 && (PID == SCP_RPID(ntohs(pkt_tx->PID)))) {
				/* 通常不进入，如果响应包是对回环包的响应 */
				seg_tx->responds = ACK_ACK1;
			}
		}

		if (seg_tx->responds != ACK_NULL) {
			/* 已正确收到响应包 */
			seg_tx->respseg = seg_rx;	/* 存放对方回复的消息 */
			seg_rx = NULL;
		}

		seg_tx = seg_tx->next;	/* 循环处理一系列发送包（很少用） */
	}

	if (seg_rx)
		scpseg_free(seg_rx);

	return 0;
}

/**
 * \brief	将已处理完的发送包从发送队列中清除
 */
static int scp_tx_remove_from_writelist(scp_t *pscp, segment_t *segment)
{
	int res;
	irq_flag lock;
	segment_t *seg;

	irq_disable(lock);
	res = ACK_ACK1;
	seg = segment;
	while (seg) {
		if (seg->responds != ACK_ACK1 && seg->responds != ACK_ACK2
			&& seg->responds != ACK_ACK3) {
			res = ACK_NACK;	/* 等待响应超时 */
		}
		list_del_init(&(seg->node));	/* 将本次消息从系列中删除 */
		seg = seg->next;	/* 循环处理一系列发送包 */
	}
	irq_enable(lock);

	return res;
}

/********************************** 接口函数 **********************************/
/**
 * \brief	客户端向服务器发送一个数据包，并等待它确认已收到消息
 * \param	pscp：	创建的通信连接
 * \param	segment:单次通信的消息，或者一系列消息
 * \return	正常响应返回ACK_ACK1，异常返回超时ACK_NACK
 */
int scpclt_send_packet(scp_t *pscp, segment_t *segment)
{
	int res, lastcnt;
	unsigned long timeout;

	scp_dev_t *pdev;			/* 通信设备 */
	segment_t *seg = segment;	/* 数据段 */

	if (!scpseg_valid(seg)	/* 要发送的消息是否有效 */
		|| !scp_valid(pscp)	/* 通信连接是否有效 */
		|| !scpdev_valid(pscp->pdev, SCP_DEVTYPE_ANY)) {	/* 通信设备是否有效 */
		return -EACCES;
	}

	pdev = pscp->pdev;	/* 获取与通信连接相匹配的通信设备 */

	/** 1. 将消息或消息系列添加到发送队列 */
	scp_tx_addto_writelist(pscp, seg);

	/** 2. 准备发送消息的包头数据 */
	scpdevs_send(segment, RWMODE_START);

run_state:

	/** 3. 从写数据包队列中取出一个要写的数据包 */
	seg = tx_readfrom_list(pscp);

	if (scpseg_valid(seg)) {
		seg->pdev = pdev;
		// seg->XTM = jiffies; //TODO: 传输时的时间tick，当前未操作
		seg->PHS |= SEGMENT_TRANSFERING;	/* 设置包状态：当前包正在发送中 */
		/** 4. 发送包 */
		res = scpdevs_send(seg, RWMODE_REQUEST); /* 客户端发送请求包 */
		if (res > 0) {
			if (!(seg->PHS & SEGMENT_TRANSMIT1))
				seg->PHS |= SEGMENT_TRANSMIT1;	/* 当前包已发送，需要等待响应 */
			else if (!(seg->PHS & SEGMENT_TRANSMIT2))
				seg->PHS |= SEGMENT_TRANSMIT2;	/* 未使用 */
			else if (!(seg->PHS & SEGMENT_TRANSMIT3))
				seg->PHS |= SEGMENT_TRANSMIT3;	/* 未使用 */
		} else {
			seg->responds = ACK_NACK;
		}
	}

	/** 5. 接收数据包响应 */
	pscp->active_segment = segment;

	/* 这里需要轮询，没有收到完整包则依靠下面的goto实现轮询阻塞 */
	scp_run_receiving(pscp, pscp->active_segment);

	/* 复位包状态 */
	lastcnt = 0;
	seg = segment;	/* 本次发送的包 */
	while (seg) {
		if (seg->responds == ACK_NULL && (seg->PHS & SEGMENT_TRANSFERING)) {
			/* 如果未收到响应包，做超时判断，超时后将发送状态取消 */
#ifdef CONFIG_SCP_USE_TIMEOUT
			//TODO: 增加超时处理
			// timeout = difft_jiffies(seg->XTM);
			// if (jiffies_to_msecs(timeout) > TCPCS_WORK_TIMEOUT) {
			// seg->PHS &= ~SEGMENT_TRANSFERING; // stop
			// }
#endif
		}

		if (seg->responds == ACK_NULL) {
			if ((!(seg->PHS & SEGMENT_TRANSFERING)) && (seg->PHS & SEGMENT_TRANSMIT3)) {
				/* 如果3次重发都没得到响应，告知发送包响应失败 */
				seg->responds = ACK_FACK;
			} else {
				lastcnt++;
			}
		}

		seg = seg->next;	/* 循环处理一系列发送包（当前未使用） */
	}

	//TODO: 当前没有增加超时机制，是在这里死循环等待数据响应
	if (lastcnt > 0)	/* 重新接收响应包，直到超时退出 */
		goto run_state;

	/* 做发送结束后的清理 */
	scpdevs_send(segment, RWMODE_FINISH);

	pscp->active_segment = NULL;	/* 发送包处理完则清空 */
	/* 将已处理完的发送包从发送队列中清除 */
	res = scp_tx_remove_from_writelist(pscp, segment);

	return res;
}

/*********************************** 文件尾 ***********************************/
